/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxParallelEdgeLayout
 *
 * Extends <mxGraphLayout> for arranging parallel edges. This layout works
 * on edges for all pairs of vertices where there is more than one edge
 * connecting the latter.
 *
 * Example:
 *
 * (code)
 * var layout = new mxParallelEdgeLayout(graph);
 * layout.execute(graph.getDefaultParent());
 * (end)
 *
 * To run the layout for the parallel edges of a changed edge only, the
 * following code can be used.
 *
 * (code)
 * var layout = new mxParallelEdgeLayout(graph);
 *
 * graph.addListener(mxEvent.CELL_CONNECTED, function(sender, evt)
 * {
 *   var model = graph.getModel();
 *   var edge = evt.getProperty('edge');
 *   var src = model.getTerminal(edge, true);
 *   var trg = model.getTerminal(edge, false);
 *
 *   layout.isEdgeIgnored = function(edge2)
 *   {
 *     var src2 = model.getTerminal(edge2, true);
 *     var trg2 = model.getTerminal(edge2, false);
 *
 *     return !(model.isEdge(edge2) && ((src == src2 && trg == trg2) || (src == trg2 && trg == src2)));
 *   };
 *
 *   layout.execute(graph.getDefaultParent());
 * });
 * (end)
 *
 * Constructor: mxParallelEdgeLayout
 *
 * Constructs a new parallel edge layout for the specified graph.
 */
function mxParallelEdgeLayout(graph)
{
    mxGraphLayout.call(this, graph);
};

/**
 * Extends mxGraphLayout.
 */
mxParallelEdgeLayout.prototype = new mxGraphLayout();
mxParallelEdgeLayout.prototype.constructor = mxParallelEdgeLayout;

/**
 * Variable: spacing
 *
 * Defines the spacing between the parallels. Default is 20.
 */
mxParallelEdgeLayout.prototype.spacing = 20;

/**
 * Function: execute
 *
 * Implements <mxGraphLayout.execute>.
 */
mxParallelEdgeLayout.prototype.execute = function(parent)
{
    var lookup = this.findParallels(parent);

    this.graph.model.beginUpdate();
    try
    {
        for (var i in lookup)
        {
            var parallels = lookup[i];

            if (parallels.length > 1)
            {
                this.layout(parallels);
            }
        }
    }
    finally
    {
        this.graph.model.endUpdate();
    }
};

/**
 * Function: findParallels
 *
 * Finds the parallel edges in the given parent.
 */
mxParallelEdgeLayout.prototype.findParallels = function(parent)
{
    var model = this.graph.getModel();
    var lookup = [];
    var childCount = model.getChildCount(parent);

    for (var i = 0; i < childCount; i++)
    {
        var child = model.getChildAt(parent, i);

        if (!this.isEdgeIgnored(child))
        {
            var id = this.getEdgeId(child);

            if (id != null)
            {
                if (lookup[id] == null)
                {
                    lookup[id] = [];
                }

                lookup[id].push(child);
            }
        }
    }

    return lookup;
};

/**
 * Function: getEdgeId
 *
 * Returns a unique ID for the given edge. The id is independent of the
 * edge direction and is built using the visible terminal of the given
 * edge.
 */
mxParallelEdgeLayout.prototype.getEdgeId = function(edge)
{
    var view = this.graph.getView();

    // Cannot used cached visible terminal because this could be triggered in BEFORE_UNDO
    var src = view.getVisibleTerminal(edge, true);
    var trg = view.getVisibleTerminal(edge, false);

    if (src != null && trg != null)
    {
        src = mxObjectIdentity.get(src);
        trg = mxObjectIdentity.get(trg);

        return (src > trg) ? trg + '-' + src : src + '-' + trg;
    }

    return null;
};

/**
 * Function: layout
 *
 * Lays out the parallel edges in the given array.
 */
mxParallelEdgeLayout.prototype.layout = function(parallels)
{
    var edge = parallels[0];
    var view = this.graph.getView();
    var model = this.graph.getModel();
    var src = model.getGeometry(view.getVisibleTerminal(edge, true));
    var trg = model.getGeometry(view.getVisibleTerminal(edge, false));

    // Routes multiple loops
    if (src == trg)
    {
        var x0 = src.x + src.width + this.spacing;
        var y0 = src.y + src.height / 2;

        for (var i = 0; i < parallels.length; i++)
        {
            this.route(parallels[i], x0, y0);
            x0 += this.spacing;
        }
    }
    else if (src != null && trg != null)
    {
        // Routes parallel edges
        var scx = src.x + src.width / 2;
        var scy = src.y + src.height / 2;

        var tcx = trg.x + trg.width / 2;
        var tcy = trg.y + trg.height / 2;

        var dx = tcx - scx;
        var dy = tcy - scy;

        var len = Math.sqrt(dx * dx + dy * dy);

        if (len > 0)
        {
            var x0 = scx + dx / 2;
            var y0 = scy + dy / 2;

            var nx = dy * this.spacing / len;
            var ny = dx * this.spacing / len;

            x0 += nx * (parallels.length - 1) / 2;
            y0 -= ny * (parallels.length - 1) / 2;

            for (var i = 0; i < parallels.length; i++)
            {
                this.route(parallels[i], x0, y0);
                x0 -= nx;
                y0 += ny;
            }
        }
    }
};

/**
 * Function: route
 *
 * Routes the given edge via the given point.
 */
mxParallelEdgeLayout.prototype.route = function(edge, x, y)
{
    if (this.graph.isCellMovable(edge))
    {
        this.setEdgePoints(edge, [new mxPoint(x, y)]);
    }
};
